﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using PoolGame2.Assets;
using PoolGame2.Physics;
using PoolGame2.Menus;
using PoolGame2.Player;
using PoolGame2.Networking;
using PoolGame2.Game;
using PoolGame2.Managers;
using PoolGame2.Controls;
using PoolGame2.Rules;

namespace PoolGame2.DebugGame
{
    public class DebugGameManager : ManagerGame, IGameControlListener
    {

        public GameObject table;
        public GameObject room;
        public Vector3[] rackPositions;
        public PoolGame2.Game.Cue poolStick;
        public Ball cueball;
        public int iCurrentlySelectedBall = 0;
        public List<GameBall> gameBalls = new List<GameBall>();

        public StateMachine stateManager;

        private Vector3 movePos = Vector3.Zero;
        
        protected CameraManager cameraManager;
        protected GameScreenOverlay screenOverlay;
        protected bool takeShot = false;

        private ReplayManager replayManager;
        //TODO: Make not a variable maybe?
        public bool isBreak = true;
        public PoolGame PGame;

        private VertexPositionColor[] indicatorPointList;
        private int[] indicatorLineListIndices;
        private VertexDeclaration indicatorVertexDeclaration;
        private BasicEffect indicatorEffect;
        
        public delegate void ControllerCallback(List<GameControlAction> actions);

        public class DebugGameState : StateMachineState
        {
            public ControllerCallback OnControllerEvent = null;
            public DebugGameState(int iStateID, EntryHandler enter, ExitHandler exit, UpdateHandler update, DrawHandler draw, ControllerCallback controller) 
                : base(iStateID, enter, exit, update, draw)
            {
                OnControllerEvent = controller;
            }
        }

        public NetworkManager netManager = null;

        public PlayerPresence[] players = new PlayerPresence[2];

        public DebugGameManager(PoolGame game)
            : base(game)
        {
            stateManager = new StateMachine(TransitionCallback);
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_LOBBY, stateLobbyEnter, stateLobbyLeave, stateLobbyUpdate, stateLobbyDraw,null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_STARTING, stateGameStartingEnter, stateGameStartingLeave, stateGameStartingUpdate, stateGameStartingDraw, null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_PLAYER_TAKING_SHOT, statePlayerShotEnter, statePlayerShotLeave, statePlayerShotUpdate, statePlayerShotDraw, null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_BALLS_ACTIVE, stateBallsActiveEnter, stateBallsActiveLeave, stateBallsActiveUpdate, stateBallsActiveDraw, null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_REPLAY_MODE, stateReplayModeEnter, stateReplayModeLeave, stateReplayModeUpdate, stateReplayModeDraw, null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_PAUSED, stateGamePausedEnter, stateGamePausedLeave, stateGamePausedUpdate, stateGamePausedDraw, null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_OVER, stateGameOverEnter, stateGameOverLeave, stateGameOverUpdate, stateGameOverDraw, null));
            stateManager.STATES.Add(new DebugGameState((int)GameState.GAME_MOVE_BALLS, statePlayerMovingBallsEnter, statePlayerMovingBallsLeave, statePlayerMovingBallsUpdate, statePlayerMovingBallsDraw, statePlayerMovingBallsControls));



            indicatorLineListIndices = new int[4];
            indicatorLineListIndices[0] = 0;
            indicatorLineListIndices[1] = 1;
            indicatorLineListIndices[2] = 2;
            indicatorLineListIndices[3] = 3;

            indicatorPointList = new VertexPositionColor[4];
            indicatorPointList[0] = new VertexPositionColor();
            indicatorPointList[0].Color = Color.White;

            indicatorPointList[1] = new VertexPositionColor();
            indicatorPointList[1].Color = Color.White;

            indicatorPointList[2] = new VertexPositionColor();
            indicatorPointList[2].Color = Color.White;

            indicatorPointList[3] = new VertexPositionColor();
            indicatorPointList[3].Color = Color.White;

            indicatorVertexDeclaration = new VertexDeclaration(game.graphics.GraphicsDevice, VertexPositionColor.VertexElements );
            indicatorEffect = new BasicEffect(game.GraphicsDevice, null);
            indicatorEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);

            cameraManager = new CameraManager(game); 
            this.PGame = game;
            players[0] = new PlayerPresence(PlayerPresence.PlayerType.PLAYER_LOCAL, PGame);
            players[0].GameState.Active = true;
            players[0].GameState.ShotsRemaining = 1;
            players[1] = new PlayerPresence(PlayerPresence.PlayerType.PLAYER_LOCAL, PGame);
            players[1].GameState.Active = false;
            PoolGame.gameControls.addListener(cameraManager);

            stateManager.Transition((int)GameState.GAME_LOBBY);
        }

        public override CameraManager GetCameraManager()
        {
            return cameraManager;
        }

        public override GameState GetState()
        {
            return (GameState)stateManager.STATE;
        }

        public override void StartGame()
        {
            stateManager.Transition((int)GameState.GAME_STARTING);
        }

        public override void EndGame()
        {
            stateManager.Transition((int)GameState.GAME_OVER);
        }

        public void TransitionCallback(int oldState, int newState)
        {
        }

        public override void Update(GameTime gameTime)
        {
            cameraManager.GetCamera().UpdateCamera(gameTime);
           
            stateManager.Update(gameTime);

            

            if (GetState() != GameState.GAME_STARTING)
            {
                PoolGame pGame = (PoolGame)Game;
                if (!PoolGame.gameControls.InMenu)
                {
                    if (PoolGame.gameControls.MenuBackAction())
                    {
                        pGame.menuManager.SwitchMenu(MenuManager.eMenuTypes.MENU_PAUSE);
                    }
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            stateManager.Draw(gameTime);
            
            PoolGame pGame = (PoolGame)Game;
            SpriteFont font = pGame.fontDatabase.loadFont("Courier New");
            int height = 5;
            Color textColor = Color.Red;
            //pGame.spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            //pGame.spriteBatch.DrawString(font, "Currently in Debug mode: Press Space to toggle states", new Vector2(10, height), textColor);
            //height += font.LineSpacing;
            //pGame.spriteBatch.DrawString(font, "Current Game State: " + GetState(), new Vector2(10, height), textColor);
            //height += font.LineSpacing;
            //pGame.spriteBatch.DrawString(font, "Camera: " + cameraManager.GetActiveCamera() + ", Position (" + cameraManager.GetCamera().GetPosition() + ")", new Vector2(10, height), textColor);
            //// output poolstick power and tracking on screen
            //if (poolStick != null)
            //{
            //    height += font.LineSpacing;
            //    pGame.spriteBatch.DrawString(font, "Shot Power: " + poolStick.GetShotPower(), new Vector2(10, height), textColor);
            //    height += font.LineSpacing;
            //    //pGame.spriteBatch.DrawString(font, "Tracking on/off: " + poolStick.IsTrackingOn(), new Vector2(10, height), textColor);
            //    //height += font.LineSpacing;
            //    pGame.spriteBatch.DrawString(font, "Cue Tip X: " + poolStick.GetCueTipPosition().X + " : Y : " + poolStick.GetCueTipPosition().Y, new Vector2(10, height), textColor);
            //}

            ////START RULES DEBUG
            //height += font.LineSpacing;
            //if (GetPlayerNumber() == 1)
            //    pGame.spriteBatch.DrawString(font, "Current Active Player: " + GetPlayerNumber(), new Vector2(10, height), Color.Orange);
            //else
            //    pGame.spriteBatch.DrawString(font, "Current Active Player: " + GetPlayerNumber(), new Vector2(10, height), Color.Yellow);
            //height += font.LineSpacing;
            //if (GetPlayerPresence().GameState.ShotsRemaining == 2)
            //{
            //    pGame.spriteBatch.DrawString(font, "Shots Remaining: " + GetPlayerPresence().GameState.ShotsRemaining, new Vector2(10, height), Color.Green);
            //}
            //else
            //{
            //    pGame.spriteBatch.DrawString(font, "Shots Remaining: " + GetPlayerPresence().GameState.ShotsRemaining, new Vector2(10, height), Color.RosyBrown);
            //}
            //height += font.LineSpacing;
            //if (GetPlayerPresence().getColours())
            //    pGame.spriteBatch.DrawString(font, "Player Ball: Colours", new Vector2(10, height), Color.Purple);
            //else if (GetPlayerPresence().getStripes())
            //    pGame.spriteBatch.DrawString(font, "Player Ball: Stripes", new Vector2(10, height), Color.Yellow);
            //else
            //    pGame.spriteBatch.DrawString(font, "Player Ball: Undecided", new Vector2(10, height), Color.Plum);
            //height += font.LineSpacing;
            ////END RULES DEBUG

            
            //pGame.spriteBatch.End();
        }


        private void stateLobbyEnter()
        {
            //PoolGame pGame = (PoolGame)Game;
            //pGame.menuManager.SwitchMenu(MenuManager.eMenuTypes.MENU_LOBBY);
        }

        private void stateLobbyUpdate(GameTime time)
        {
        }

        private void stateLobbyDraw(GameTime time)
        {
        }

        private void stateLobbyLeave()
        {
            if (GetState() == GameState.GAME_OVER)
                return;
        }

        /*
         * State for begining a game
         */
        private void stateGameStartingEnter()
        {
            PoolGame pGame = (PoolGame)Game;
            pGame.menuManager.SwitchMenu(MenuManager.eMenuTypes.MENU_NONE);
        }

        private void stateGameStartingUpdate(GameTime time)
        {
            stateManager.Transition((int)GameState.GAME_PLAYER_TAKING_SHOT);
        }

        private void stateGameStartingDraw(GameTime time)
        {
        }

        private void stateGameStartingLeave()
        {
            if (GetState() == GameState.GAME_OVER)
                return;

            InitialiseTable();

        }

        /*
         * State for player shooting
         */
        private void statePlayerShotEnter()
        {
            //((PoolGame)(Game)).eventManager.FireEvent(new GameEvent((int)GameEventID.NIXON_SAY, "Player Shot"));
        }

        private void statePlayerShotUpdate(GameTime time)
        {
            if (GetState() != GameState.GAME_STARTING && GetState() != GameState.GAME_LOBBY)
            {
                if (!PoolGame.gameControls.InMenu)
                {
                    // Ensure tracking and power being updated correctly
                    poolStick.Update(time);

                    // take shot
                    if (this.takeShot)
                    {
                        Vector3 powerAndDirection = poolStick.GetUnitVectorDirection() * poolStick.GetShotPower();
                        Vector3 cueTipPosition = poolStick.GetCueTipPosition();

                        // record info for replay
                        replayManager.recordPreShotState();
                        replayManager.recordCueBallStrike(powerAndDirection, cueTipPosition);


                        cueball.Strike(powerAndDirection, cueTipPosition);
                        stateManager.Transition((int)GameState.GAME_BALLS_ACTIVE);
                        
                        PoolGame.audioDBManger.PlaySoundEffect(SoundEffectType.CUE_HITS_BALL);

                        // reset power and cue tip positon
                        poolStick.ResetPowerAndCueTipPosition();
                        takeShot = false;
                    }                    
                }
            }

        }

        private void statePlayerShotDraw(GameTime time)
        {
            DrawWorld(time);
            DrawShotIndicator(time, cueball.getPosition(), poolStick.GetUnitVectorDirection() * -1.0f );
        }

        private void statePlayerShotLeave()
        {
        }

        /*
         * State for when the balls are moving (usually after a shot)
         */
        private void stateBallsActiveEnter()
        {
            cameraManager.setAutoMangage(true);
        }

        private void stateBallsActiveUpdate(GameTime time)
        {
            if (PoolGame.gameControls.InMenu)
            {
                //if (pGame.gameControls.IsKeyPressed(Keys.Space))
                if (PoolGame.gameControls.PlayerTakingShot())
                {
                    stateManager.Transition((int)GameState.GAME_PLAYER_TAKING_SHOT);
                }
            }

            UpdateWorld(time);

            if (physicsRuntime.AreBallsMoving() == false)
            {
                replayManager.recordPostShotState();
                stateManager.Transition((int)GameState.GAME_PLAYER_TAKING_SHOT);
            }
        }

        private void stateBallsActiveDraw(GameTime time)
        {
            DrawWorld(time);
        }

        private void stateBallsActiveLeave()
        {
            cameraManager.setAutoMangage(false);
            List<RuleC.RuleTasks> tasks = physicsRuntime.getTasks();
            SetBreak(false);
            if (physicsRuntime.collideOnce == false)
                tasks.Add(RuleC.RuleTasks.TWO_SHOTS);
            //TODO: Make not a hack
            foreach (Ball ball in physicsRuntime.getState().getBalls())
            {
                if (ball.getNumber() == 0)
                {
                    ball.setPocketed(false);
                    ball.setVisible(true);
                }
                if (ball.getPocketed())
                    ball.setVisible(false);
            }
            /*
             * Hierarchy:
             * Rerack
             * Game Loss
             * Game Win
             * Reset cueball
             * Two shots
             * Pots both stripe and colour
             * First colour/stripe
             * 
             */
            if (tasks.Contains(RuleC.RuleTasks.RERACK))
            {
                GivePlayerShots(0); //set current players shots = 0
                InitialiseTable(); //Rerack
                SwitchActivePlayer(); //Switch active player
                GivePlayerShots(1); //set current players shots = 0
            }else if (tasks.Contains(RuleC.RuleTasks.KILL_GAME)){
                KillGame();
                //kill the game
            }else if (tasks.Contains(RuleC.RuleTasks.WIN_GAME)){
                WinGame();
                //win game
            }else if (tasks.Contains(RuleC.RuleTasks.RESET_CUEBALL)){
                //TODO: HACK CITY
                //reset cueball
                foreach (Ball ball in physicsRuntime.getState().getBalls()){
                    if (ball.getNumber() == 0)
                    {
                        ball.setPosition(new Vector3(36, 52.8f, -5));
                    }
                }
                GivePlayerShots(0); //set current players shots = 0
                SwitchActivePlayer(); //switch active player
                GivePlayerShots(2);//give player two shots
            }
            else if (tasks.Contains(RuleC.RuleTasks.TWO_SHOTS))
            {
                GivePlayerShots(0); //set current players shots = 0
                SwitchActivePlayer(); //switch active player
                GivePlayerShots(2);//give player two shots

                //output message and play bad shot sounds
                screenOverlay.SetOverlayMessage(OverlayMessageType.BAD_SHOT);
                PoolGame.audioDBManger.PlaySoundEffect(SoundEffectType.BAD_SHOT);
                
            }
            else if (tasks.Contains(RuleC.RuleTasks.CHOOSE_STRIPES) && tasks.Contains(RuleC.RuleTasks.CHOOSE_COLOUR))
            {
                //TODO: Get the player to choose which colour they want to be
                GetPlayerPresence().setColours(); //set active player as colours
                SwitchActivePlayer(); //switch active player
                GetPlayerPresence().setStripes(); //set other player as stripes
                SwitchActivePlayer(); //switch back to original player
                GivePlayerShots(1); //continue playing
            }
            else if (tasks.Contains(RuleC.RuleTasks.CHOOSE_COLOUR))
            {
                GetPlayerPresence().setColours(); //set active player as colours
                SwitchActivePlayer(); //switch active player
                GetPlayerPresence().setStripes(); //set other player as stripes
                SwitchActivePlayer(); //switch back to original player
                GivePlayerShots(1); //continue playing
            }
            else if (tasks.Contains(RuleC.RuleTasks.CHOOSE_STRIPES))
            {
                GetPlayerPresence().setStripes(); //set active player as colours
                SwitchActivePlayer(); //switch active player
                GetPlayerPresence().setColours(); //set other player as stripes
                SwitchActivePlayer(); //switch back to original player
                GivePlayerShots(1); //continue playing
            }
            else if (tasks.Contains(RuleC.RuleTasks.POT_VALID))
            {
                GivePlayerShots(1); // Player potted a ball, give them another shot
                // Shots don't carry, so this will always get set to one, even if the
                // players current shots == 2
            }
            else if (tasks.Contains(RuleC.RuleTasks.OK))
            {
                //the player didn't mess up, but didn't pot a ball
                GivePlayerShots(GetPlayerPresence().GameState.ShotsRemaining - 1); //decrement, but don't switch player
            }

            if (GetPlayerPresence().GameState.ShotsRemaining < 1)
            {
                SwitchActivePlayer();
                GivePlayerShots(1);
            }
            physicsRuntime.resetTasks();


            screenOverlay.SetOverlayMessage(OverlayMessageType.REPLAY_SHOT);

        }

        /*
         * State for when the game is in replay mode
         */
        private void stateReplayModeEnter()
        {            
            replayManager.StartReplay();
        }

        private void stateReplayModeUpdate(GameTime time)
        {
            replayManager.UpdateReplay(time);
            UpdateWorld(time);

            if (!replayManager.IsReplaying)
            {
                stateManager.Transition((int)GameState.GAME_PLAYER_TAKING_SHOT);
            }
        }

        private void stateReplayModeDraw(GameTime time)
        {
            DrawWorld(time);
        }

        private void stateReplayModeLeave()
        {
        }

        /*
         * State for player moving balls about
         */
        private void statePlayerMovingBallsEnter()
        {
            iCurrentlySelectedBall = 0;
            movePos = gameBalls[iCurrentlySelectedBall].getPosition();
        }

        private void statePlayerMovingBallsUpdate(GameTime time)
        {
            if (PoolGame.gameControls.InMenu)
                return;
        }

        public void statePlayerMovingBallsControls(List<GameControlAction> actions)
        {
            Ball phys = (Ball)(gameBalls[iCurrentlySelectedBall].getPhysicsObject());
            Matrix rotmat = Matrix.CreateFromYawPitchRoll(gameBalls[iCurrentlySelectedBall].getRotation().Y, gameBalls[iCurrentlySelectedBall].getRotation().X, gameBalls[iCurrentlySelectedBall].getRotation().Z);
            Vector3 rot = rotmat.Forward;
            Vector3 right = rotmat.Right;
            Vector3 pos = phys.getPosition();

            foreach (GameControlAction action in actions)
            {
                if (action.GetAction() == ControlActions.DPAD_UP)
                {
                    stateManager.Transition((int)GameState.GAME_PLAYER_TAKING_SHOT);
                }
                else if (action.GetAction() == ControlActions.DPAD_DOWN)
                {
                    //stateManager.Transition((int)GameState.GAME_PLAYER_TAKING_SHOT);
                }
                else if (action.GetAction() == ControlActions.DPAD_LEFT)
                {
                    iCurrentlySelectedBall = (iCurrentlySelectedBall - 1) < 0 ? gameBalls.Count - 1 : (iCurrentlySelectedBall - 1);
                    movePos = gameBalls[iCurrentlySelectedBall].getPosition();
                }
                else if (action.GetAction() == ControlActions.DPAD_RIGHT)
                {
                    iCurrentlySelectedBall = (iCurrentlySelectedBall + 1) % gameBalls.Count;
                    movePos = gameBalls[iCurrentlySelectedBall].getPosition();
                }
                else if (action.GetAction() == ControlActions.MOVE_FORWARD)
                {
                    movePos += (rot * 2.0f);
                    physicsRuntime.CanMoveObjectTo(phys, movePos);
                }
                else if (action.GetAction() == ControlActions.MOVE_BACK)
                {
                    movePos -= (rot * 2.0f);
                    physicsRuntime.CanMoveObjectTo(phys, movePos);
                }
                else if (action.GetAction() == ControlActions.STRAFE_LEFT)
                {
                    movePos -= (right * 2.0f);
                    physicsRuntime.CanMoveObjectTo(phys, movePos);
                }
                else if (action.GetAction() == ControlActions.STRAFE_RIGHT)
                {
                    movePos += (right * 2.0f);
                    physicsRuntime.CanMoveObjectTo(phys, movePos);
                }
            }
        }

        private void statePlayerMovingBallsDraw(GameTime time)
        {
            DrawWorld(time);

            ModelObject modelObject = gameBalls[iCurrentlySelectedBall].getModelObject();
            if (modelObject != null)
            {
                Model model = modelObject.getModel();
                // Copy any parent transforms.
                Matrix[] transforms = new Matrix[model.Bones.Count];
                model.CopyAbsoluteBoneTransformsTo(transforms);

                if (gameBalls[iCurrentlySelectedBall].getPhysicsObject() != null)
                {
                    PhysicsObject physicsObject = gameBalls[iCurrentlySelectedBall].getPhysicsObject();
                    for (int i = 0; i < transforms.Length; i++)
                    {
                        if (physicsObject is Ball)
                        {
                            transforms[i] *= ((Ball)physicsObject).getOrientation();
                        }
                        transforms[i] *= Matrix.CreateScale(1.1f) * Matrix.CreateTranslation(movePos);
                    }
                    
                }

                PoolGame game = (PoolGame)this.Game;
                foreach (ModelMesh mesh in modelObject.getModel().Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.PreferPerPixelLighting = true;
                        effect.DirectionalLight0.Direction = Vector3.Down;
                        effect.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 0.0f, 0.0f);
                        effect.DirectionalLight0.SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);
                        effect.DirectionalLight0.Enabled = true;
                        effect.SpecularPower = 50;
                        effect.World = transforms[mesh.ParentBone.Index] * gameBalls[iCurrentlySelectedBall].getWorldMatrix();
                        effect.Projection = game.gameManager.GetCameraManager().GetCamera().projectionMatrix;
                        effect.View = game.gameManager.GetCameraManager().GetCamera().viewMatrix;
                    }
                    mesh.Draw();
                }
            }
        }

        private void statePlayerMovingBallsLeave()
        {
        }

        /*
         * State for when the game is paused
         */
        private void stateGamePausedEnter()
        {
        }

        private void stateGamePausedUpdate(GameTime time)
        {
        }

        private void stateGamePausedDraw(GameTime time)
        {
            DrawWorld(time);
        }

        private void stateGamePausedLeave()
        {
        }

        /*
         * State for when the game is over
         */
        private void stateGameOverEnter()
        {
            PoolGame pGame = (PoolGame)Game;
            pGame.menuManager.SwitchMenu(MenuManager.eMenuTypes.MENU_MAIN);
            pGame.menuManager.SwitchMenu(MenuManager.eMenuTypes.MENU_SIGN_IN);
        }

        private void stateGameOverUpdate(GameTime time)
        {
        }

        private void stateGameOverDraw(GameTime time)
        {
        }

        private void stateGameOverLeave()
        {
        }

        private void DrawWorld(GameTime time)
        {
            room.Draw(time);
            table.Draw(time);
            physicsRuntime.Draw(time);
            if (!PoolGame.gameControls.InMenu)
            {
                screenOverlay.Draw(time);
            }
            /*
            //display replay shot query
            if (replayManager != null && replayManager.IsShowingReplayMsg)
            {
                screenOverlay.SetOverlayMessage(OverlayMessageType.REPLAY_SHOT,1000);
            }*/
        }

        private void DrawShotIndicator(GameTime time, Vector3 ballPos, Vector3 shotDir)
        {
            float length = 2.0f;
            shotDir.Y = 0.0f;
            Vector3 currPos = ballPos + (shotDir * length);
            Vector3 rightDir = Vector3.Cross(Vector3.Up, shotDir) * 0.5f;
            PoolGame pGame = (PoolGame)Game;
            Physics.Ball hitBall = null;
            Physics.Ball ignoreBall = cueball;

            pGame.GraphicsDevice.VertexDeclaration = indicatorVertexDeclaration;

            indicatorEffect.World = table.getWorldMatrix();
            indicatorEffect.View = cameraManager.GetCamera().viewMatrix;
            indicatorEffect.Projection = cameraManager.GetCamera().projectionMatrix;

            indicatorEffect.Begin();
            foreach (EffectPass pass in indicatorEffect.CurrentTechnique.Passes)
            {
                //pGame.spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                pass.Begin();
                for (int i = 0; i < 10; i++)
                {
                    Vector3 pos1 = currPos;
                    Vector3 pos2 = currPos + (shotDir * 5.0f);

                    indicatorPointList[0].Position = pos1 - (rightDir);
                    indicatorPointList[1].Position = pos1 + (rightDir);
                    indicatorPointList[2].Position = pos2 - (rightDir);
                    indicatorPointList[3].Position = pos2 + (rightDir);

                    for (int j = 0; j < 25; j++ )
                    {
                        currPos += (shotDir * 0.25f);
                        if (physicsRuntime.IsAreaEmpty(ignoreBall, currPos, out hitBall) == false)
                        {
                            Vector3 norm = (currPos - hitBall.getPosition());
                            norm.Normalize();
                            shotDir = -Vector3.Reflect(shotDir * 0.5f, norm);
                            shotDir.Normalize();
                            //shotDir = Vector3.Cross( hitBall.getPosition() - currPos, shotDir ); 
                            rightDir = Vector3.Cross(Vector3.Up, shotDir) * 0.5f;

                            ignoreBall = hitBall;
                            currPos = hitBall.getPosition();
                        }
                    }

                    pGame.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                         PrimitiveType.TriangleStrip,
                         indicatorPointList, 0, 4,
                         indicatorLineListIndices, 0, 2);
                }
                pass.End();
            }
            indicatorEffect.End();
            //pGame.spriteBatch.End();
        }

        private void UpdateWorld(GameTime time)
        {
            table.Update(time);
            physicsRuntime.doPhysics((float)time.ElapsedGameTime.TotalSeconds, table);
        }

        private void InitialiseTable()
        {
            CreateRackPositions();

            PoolGame pgame = (PoolGame)Game;

            ModelObject roomScene = new ModelObject(pgame.modelDatabase.loadModel("models\\poolroom"));
            room = new GameObject(pgame, roomScene, null, "room");
        
            
            ModelObject terrModel = new ModelObject(pgame.modelDatabase.loadModel("models\\table"));
            table = new GameObject(pgame, terrModel, null, "table");

            gameBalls.Clear();

            ModelObject newModel;
            GameBall loadedBall;

            cueball = new Ball(BallType.CUE_BALL, 0, PhysicsConstants.DEFAULT_RADIUS);
            cueball.setPosition(new Vector3(36, 52.8f, -5));
            newModel = new ModelObject(pgame.modelDatabase.loadModel("models\\cueball"));
            loadedBall = new GameBall(pgame, newModel, cueball, "cueball");
            table.addChild(loadedBall);
            
            newModel = new ModelObject(pgame.modelDatabase.loadModel("models\\poolstick"));
            poolStick = new PoolGame2.Game.Cue(pgame, newModel, null, loadedBall);
            PoolGame.gameControls.addListener(poolStick);
            table.addChild(poolStick);
            cameraManager.AddCamera(new CueCamera(pgame,poolStick), true);
            cameraManager.AddAutoManagedCamera(new ObjectTrackingCamera(pgame, new Vector3(-100, 200, -100), Vector3.Zero, Vector3.Up, cueball));
            cameraManager.GetCamera().LookAtObject(loadedBall, false, 300.0f);

            physicsRuntime = new PhysicsRuntime(poolStick, table, pgame, cameraManager);
            PoolGame.gameControls.addListener(physicsRuntime);
            physicsRuntime.RegisterForCollisionDetection(cueball);

            replayManager = new ReplayManager(physicsRuntime, table);


            for (int i = 1; i <= 15; i++)
            {
                Ball ball;
                if (i < 8)
                {
                    ball = new Ball(BallType.SPOT_BALL, i, PhysicsConstants.DEFAULT_RADIUS);
                }
                else if (i == 8)
                {
                    ball = new Ball(BallType.BLACK_BALL, i, PhysicsConstants.DEFAULT_RADIUS);
                }
                else
                {
                    ball = new Ball(BallType.STRIPPED_BALL, i, PhysicsConstants.DEFAULT_RADIUS);
                }
                ball.setPosition(rackPositions[GameConstants.RACK_ORDER[i - 1] - 1]);
                newModel = new ModelObject(pgame.modelDatabase.loadModel("models\\ball" + (i)));
                loadedBall = new GameBall(pgame, newModel, ball, "ball");

                gameBalls.Add(loadedBall);

                table.addChild(loadedBall);
                physicsRuntime.RegisterForCollisionDetection(ball);
            }

            screenOverlay = new GameScreenOverlay(pgame.textureDatabase, pgame, cameraManager, poolStick);

            physicsRuntime.RegisterForCollisionDetection(new Cushion(PhysicsConstants.CUSHION_TOP_START, PhysicsConstants.CUSHION_TOP_MAIN_START, PhysicsConstants.CUSHION_TOP_MAIN_END, PhysicsConstants.CUSHION_TOP_END));
            physicsRuntime.RegisterForCollisionDetection(new Cushion(PhysicsConstants.CUSHION_TOP_LEFT_START, PhysicsConstants.CUSHION_TOP_LEFT_MAIN_START, PhysicsConstants.CUSHION_TOP_LEFT_MAIN_END, PhysicsConstants.CUSHION_TOP_LEFT_END));
            physicsRuntime.RegisterForCollisionDetection(new Cushion(PhysicsConstants.CUSHION_BOTTOM_LEFT_START, PhysicsConstants.CUSHION_BOTTOM_LEFT_MAIN_START, PhysicsConstants.CUSHION_BOTTOM_LEFT_MAIN_END, PhysicsConstants.CUSHION_BOTTOM_LEFT_END));
            physicsRuntime.RegisterForCollisionDetection(new Cushion(PhysicsConstants.CUSHION_BOTTOM_START, PhysicsConstants.CUSHION_BOTTOM_MAIN_START, PhysicsConstants.CUSHION_BOTTOM_MAIN_END, PhysicsConstants.CUSHION_BOTTOM_END));
            physicsRuntime.RegisterForCollisionDetection(new Cushion(PhysicsConstants.CUSHION_BOTTOM_RIGHT_START, PhysicsConstants.CUSHION_BOTTOM_RIGHT_MAIN_START, PhysicsConstants.CUSHION_BOTTOM_RIGHT_MAIN_END, PhysicsConstants.CUSHION_BOTTOM_RIGHT_END));
            physicsRuntime.RegisterForCollisionDetection(new Cushion(PhysicsConstants.CUSHION_TOP_RIGHT_START, PhysicsConstants.CUSHION_TOP_RIGHT_MAIN_START, PhysicsConstants.CUSHION_TOP_RIGHT_MAIN_END, PhysicsConstants.CUSHION_TOP_RIGHT_END));

            physicsRuntime.RegisterForCollisionDetection(new Pocket(PhysicsConstants.POCKET_TOP_LEFT));
            physicsRuntime.RegisterForCollisionDetection(new Pocket(PhysicsConstants.POCKET_TOP_RIGHT));
            physicsRuntime.RegisterForCollisionDetection(new Pocket(PhysicsConstants.POCKET_CENTER_LEFT));
            physicsRuntime.RegisterForCollisionDetection(new Pocket(PhysicsConstants.POCKET_CENTER_RIGHT));
            physicsRuntime.RegisterForCollisionDetection(new Pocket(PhysicsConstants.POCKET_BOTTOM_LEFT));
            physicsRuntime.RegisterForCollisionDetection(new Pocket(PhysicsConstants.POCKET_BOTTOM_RIGHT));
        }

        private void CreateRackPositions()
        {
            float rad = 5.5f;
            // Simple phythagorean theorem
            float xChange = (float)Math.Sqrt((rad * rad) - ((rad / 2) * (rad / 2)));
            float zChange = rad;
            Vector3 rackPoint = new Vector3(-37.0f, 52.8f, -5.0f);

            rackPositions = new Vector3[15];
            // row 1
            rackPositions[0] = rackPoint;
            // row 2
            rackPositions[1] = rackPoint + new Vector3(-xChange, 0.0f, -zChange / 2);
            rackPositions[2] = rackPoint + new Vector3(-xChange, 0.0f, zChange / 2);
            // row3
            rackPositions[3] = rackPoint + new Vector3(-xChange * 2, 0.0f, -zChange);
            rackPositions[4] = rackPoint + new Vector3(-xChange * 2, 0.0f, 0);
            rackPositions[5] = rackPoint + new Vector3(-xChange * 2, 0.0f, zChange);
            // row 4
            float left = -zChange - zChange / 2;
            rackPositions[6] = rackPoint + new Vector3(-xChange * 3, 0.0f, left);
            rackPositions[7] = rackPoint + new Vector3(-xChange * 3, 0.0f, left + zChange);
            rackPositions[8] = rackPoint + new Vector3(-xChange * 3, 0.0f, left + zChange * 2);
            rackPositions[9] = rackPoint + new Vector3(-xChange * 3, 0.0f, left + zChange * 3);
            // row 5
            rackPositions[10] = rackPoint + new Vector3(-xChange * 4, 0.0f, -zChange * 2);
            rackPositions[11] = rackPoint + new Vector3(-xChange * 4, 0.0f, -zChange);
            rackPositions[12] = rackPoint + new Vector3(-xChange * 4, 0.0f, 0);
            rackPositions[13] = rackPoint + new Vector3(-xChange * 4, 0.0f, zChange);
            rackPositions[14] = rackPoint + new Vector3(-xChange * 4, 0.0f, zChange * 2);

        }

        public override void GameControlPerformed(List<GameControlAction> actions)
        {
            if (stateManager.GetCurrentState() == null || ((DebugGameState)(stateManager.GetCurrentState())).OnControllerEvent == null)
            {
                foreach (GameControlAction action in actions)
                {
                    ControlActions a = action.GetAction();
                    if (a == ControlActions.END_SHOT)
                    {
                        takeShot = true;
                    }
                    else if (a == ControlActions.REPLAY_YES)
                    {
                        stateManager.Transition((int)GameState.GAME_REPLAY_MODE);
                    }                    
                    else if (a == ControlActions.DPAD_DOWN)
                    {
                        stateManager.Transition((int)GameState.GAME_MOVE_BALLS);
                    }
                }
            }
            else
            {
                ((DebugGameState)(stateManager.GetCurrentState())).OnControllerEvent(actions);
            }
        }

        public override PlayerPresence GetPlayerPresence()
        {
            if (netManager != null)
            {
                if (netManager.connectedPlayers[0].GameState.Active)
                    return netManager.connectedPlayers[0];
                else if (netManager.connectedPlayers[1].GameState.Active)
                    return netManager.connectedPlayers[1];
                else
                    return null;
            }
            else
            {
                if (players[0].GameState.Active)
                    return players[0];
                else
                    return players[1];
            }
        }

        public override PlayerPresence GetPlayerPresence(int i){

            if (netManager != null)
            {
                return netManager.connectedPlayers[i];
            }
            else
            {
                return players[i];
            }
        }

        //TODO: There's probably a better way of doing this
        public override int GetPlayerNumber()
        {
            if (netManager != null)
            {
                if (netManager.connectedPlayers[0].GameState.Active)
                    return 0;
                else if (netManager.connectedPlayers[1].GameState.Active)
                    return 1;
                else
                    return -1;
            }
            else
            {
                if (players[0].GameState.Active)
                    return 0;
                else
                    return 1;
            }
        }

        public void SwitchActivePlayer()
        {
            if (netManager != null)
            {
                if (netManager.connectedPlayers[0].GameState.Active){
                    netManager.connectedPlayers[0].GameState.Active = false;
                    netManager.connectedPlayers[1].GameState.Active = true;
                }
                else if (netManager.connectedPlayers[1].GameState.Active)
                {
                    netManager.connectedPlayers[0].GameState.Active = true;
                    netManager.connectedPlayers[1].GameState.Active = false;
                }
            }
            else
            {
                if (players[0].GameState.Active)
                {
                    players[0].GameState.Active = false;
                    players[1].GameState.Active = true;
                }
                else
                {
                    players[0].GameState.Active = true;
                    players[1].GameState.Active = false;
                }
            }
        }

        public void GivePlayerShots(int shots)
        {
            GetPlayerPresence().GameState.ShotsRemaining = shots;
        }

        public override PhysicsRuntime GetPhysicsRuntime()
        {
            return physicsRuntime;
        }

        public override bool GetBreak()
        {
            return isBreak;
        }

        public override void SetBreak(bool val)
        {
            isBreak = val;
        }

        public void KillGame()
        {
            //Game lose code here
            stateManager.Transition((int)GameState.GAME_OVER);
        }

        public void WinGame()
        {
            //Game win code here
            stateManager.Transition((int)GameState.GAME_OVER);
        }
    }
}

